﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Cci;

using PartialExpressionCompleter.Util;
using PartialExpressionCompleter.Experiment;

namespace PartialExpressionCompleter.Query
{
    class QueryContext<E> where E : IExpression
    {
        private readonly ITypeDefinition type;
        private readonly SavedExpression<E> savedCall;
        private readonly Dictionary<IdentifiedTypeTerm, TypeTerm> typeMeanings;
        private readonly Dictionary<IExpression, HashSet<TypeTerm>> expMeanings
            = new Dictionary<IExpression, HashSet<TypeTerm>>();

        public QueryContext(ITypeDefinition type, SavedExpression<E> savedCall,
                            Dictionary<IdentifiedTypeTerm, TypeTerm> typeMeanings)
        {
            this.type = type;
            this.savedCall = savedCall;
            this.typeMeanings = typeMeanings;
        }

        public ITypeDefinition ContainingType { get { return type; } }
        public Dictionary<IdentifiedTypeTerm, TypeTerm> TypeMeanings { get { return typeMeanings; } }
        public E OriginalExpression { get { return savedCall.Expression; } }
        public Expression OriginalExpressionAsQuery { get { return savedCall.Expression.AsExpression(this); } }
        public IMethodDefinition ContainingMethod { get { return savedCall.ContainingMethod; } }
        public IEnumerable<ILocalDefinition> Locals { get { return savedCall.Locals; } }

        public bool IsMemberVisible(ITypeDefinitionMember member)
        {
            return member.IsMemberVisibleFrom(ContainingType);
        }

        public HashSet<TypeTerm> GetExpressionMeanings(IExpression exp)
        {
            HashSet<TypeTerm> res;
            if (!expMeanings.TryGetValue(exp, out res))
            {
                res = savedCall.GetExpressionMeanings(exp);
                expMeanings[exp] = res;
            }
            return res;
        }

        public HashSet<TypeTerm> GetMeaningsInContext(Expression exp)
        {
            if (exp.Meanings == null)
            {
                if (exp.TypeMember != null)
                {
                    exp.Meanings = exp.TypeMember.AsTypeTerm().SingletonSet<TypeTerm>();
                }
                else
                {
                    return new HashSet<TypeTerm>();
                }
            }
            return this.TypeMeanings.GetAllValuesSet(
                exp.Meanings.Where(m => m is IdentifiedTypeTerm)
                            .Cast<IdentifiedTypeTerm>());
        }

        private Dictionary<string, List<ITypeDefinitionMember>> visibleZeroArgTypeMembers =
            new Dictionary<string, List<ITypeDefinitionMember>>();
        public IEnumerable<ITypeDefinitionMember> VisibleZeroArgTypeMembers(ITypeDefinition type)
        {
            if (type is IManagedPointerType)
            {
                return this.VisibleZeroArgTypeMembers((type as IManagedPointerType)
                        .TargetType.ResolvedType);
            }
            string key = type.ToMangledString();
            List<ITypeDefinitionMember> res;
            if (!visibleZeroArgTypeMembers.TryGetValue(key, out res))
            {
                res = _VisibleZeroArgTypeMembers(type).ToList();
                visibleZeroArgTypeMembers[key] = res;
            }
            return res;
        }
        private IEnumerable<ITypeDefinitionMember> _VisibleZeroArgTypeMembers(ITypeDefinition type)
        {
            foreach (var field in type.GetAllFields())
            {
                if (this.IsMemberVisible(field))
                {
                    yield return field;
                }
            }
            foreach (var prop in type.GetAllProperties())
            {
                if (this.IsMemberVisible(prop))
                {
                    yield return prop;
                }
            }
            foreach (var meth in type.GetInstanceMethods())
            {
                if (meth.ParameterCount == 0 && this.IsMemberVisible(meth))
                {
                    yield return meth;
                }
            }
        }
    }
}
